package spe.xml;

import java.beans.PropertyChangeEvent;
import java.beans.PropertyChangeListener;
import java.io.ByteArrayInputStream;
import java.io.IOException;
import java.io.StringWriter;
import java.io.Writer;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;

import org.eclipse.core.resources.IFile;
import org.eclipse.core.runtime.CoreException;
import org.eclipse.core.runtime.IConfigurationElement;
import org.eclipse.draw2d.geometry.Point;
import org.eclipse.jface.resource.ImageDescriptor;
import org.eclipse.ui.IEditorInput;
import org.eclipse.ui.IFileEditorInput;
import org.w3c.dom.Attr;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import org.w3c.dom.NamedNodeMap;
import org.w3c.dom.Node;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import spe.Logger;
import spe.model.notation.Link;
import spe.model.notation.SPEMDiagram;
import spe.model.spem.MethodConfiguration;
import spe.model.spem.MethodContentElement;
import spe.model.spem.MethodContentPackage;
import spe.model.spem.MethodLibrary;
import spe.model.spem.MethodPlugin;
import spe.model.spem.NodeElement;
import spe.model.spem.PackageElement;
import spe.model.spem.ProcessPackage;
import spe.model.spem.ProcessPackageableElement;
import spe.model.spem.SPEMElement;
import spe.model.spem.SPEMFactory;
import spe.model.spem.SPERootElement;
import spe.model.registry.SPERegistryRegistry;
import spe.model.registry.SPEXmlAdapterRegistry;
import spe.ui.editor.SPEEditor;

/*
  	Class nay duoc su dung lam content provider cho editor
  	khi editor muon cap nhat noi dung model o muc xml thi se yeu cau class nay thuc hien
 */
public class SPEXmlProvider 
{
	private static DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance();
	private static TransformerFactory transformerFactory = TransformerFactory.newInstance();
	private SPEMFactory factory = null;
	private SPEEditor editor;
	private SPEXmlAdapterRegistry xmlRegistry;
	private SPERootElement rootSPEElement = null;
	
	public SPEXmlProvider(SPEMFactory fact, SPEEditor edtr)
	{
		factory = fact;
		editor = edtr;
		xmlRegistry = SPERegistryRegistry.getXmlAdapterRegistry(editor.getEditorId());
	}
		
	protected String getRootElement() 
	{
		return "<spe-root xmlns:xml=\"http://www.w3.org/2001/XML\" project-name=\"project01\" />";
	}
	
	protected Element getRootElement(Document doc) 
	{
		return doc.getDocumentElement();
	}
		
	protected String createInitialNotationInfo() 
	{
		StringBuffer buffer = new StringBuffer();
		buffer.append("<?xml version=\"1.0\" encoding=\"UTF-8\"?>");
		buffer.append("\n\n");
		buffer.append(getRootElement());
		return buffer.toString();
	}

	protected String getFileName(String semanticInfoFileName) 
	{
		return ".gpd." + semanticInfoFileName;
	}

	protected String getSemanticInfoFileName(String notationInfoFileName) 
	{
		return notationInfoFileName.substring(5);
	}
	
	public Document getDocument(IFile file)
	{
		DocumentBuilder builder;
		Document doc = null;
		try {
			builder = documentBuilderFactory.newDocumentBuilder();
			doc = builder.parse(file.getContents());
		} catch (ParserConfigurationException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}catch (SAXException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}  catch (IOException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		} catch (CoreException e) {
			// TODO Auto-generated catch block
			e.printStackTrace();
		}
		return doc;
	}
	
	
	private MethodLibrary getMethodLibrary(Element rootNode)
	{
		MethodLibrary ml = null;
		ml = (MethodLibrary) factory.createElementById("spe.model.spem.method.plugin.MethodLibrary");
			
		//get method configurations tag
		List<MethodConfiguration> listConfigs = getMethodConfigurations((Element)rootNode);
		ml.setOwnedConfigurations((List<MethodConfiguration>)listConfigs);
			
		//get method plug-ins tag
		List<MethodPlugin> listPlugins = getMethodPlugins((Element)rootNode);
		ml.setOwnedMethodPlugins(listPlugins);
	
		return ml;
	}
	
	private List<MethodConfiguration> getMethodConfigurations(Element notationElement)
	{
		List<MethodConfiguration> result = new ArrayList<MethodConfiguration>();
		NodeList allConfigElements = notationElement.getElementsByTagName("method-configuration");
		MethodConfiguration speEl = null;
		String name;
		
		for (int i = 0; i < allConfigElements.getLength(); i++)	 
		{
			String id = ((Element)allConfigElements.item(i)).getAttribute("id");
			name = ((Element)allConfigElements.item(i)).getAttribute("name");
			
			speEl = (MethodConfiguration) factory.createElementById("spe.model.spem.method.plugin.MethodConfiguration");
			speEl.setId(id);
			speEl.setName(name);
			
			result.add(speEl);
		}
		
		return result;
	}
		
	private List<MethodPlugin> getMethodPlugins(Element notationElement) 
	{
		List<MethodPlugin> result = new ArrayList<MethodPlugin>();
		NodeList allPluginElements = notationElement.getElementsByTagName("method-plugin");
		MethodPlugin speEl = null;
		String name;
		
		for (int i = 0; i < allPluginElements.getLength(); i++) 
		{
			String id = ((Element)allPluginElements.item(i)).getAttribute("id");
			
			speEl = (MethodPlugin) factory.createElementById("");
			name = ((Element)allPluginElements.item(i)).getAttribute("name");
			speEl.setName(name);
			speEl.setId(id);
					
			Element methodPluginElement = (Element)allPluginElements.item(i);
			//Read method content packages
					
			//System.out.println(String.format("notationElement %d %s %s", i, methodPluginElement.getNodeName(), node));
			List<MethodContentPackage> listContentPackages = getMethodContentElements(methodPluginElement);
			speEl.setOwnedMethodContentPackages(listContentPackages);
					
			//Read process packages
			List<ProcessPackage> listProcessPackages = getProcessPackagesElements(methodPluginElement);
			speEl.setOwnedProcessPackages(listProcessPackages);
			result.add(speEl);
		}
		return result;
	}
	
	private List<MethodContentPackage> getMethodContentElements(Element notationElement)
	{
		List<MethodContentPackage> result = new ArrayList<MethodContentPackage>();
		NodeList allPluginElements = notationElement.getElementsByTagName("method-content-package");
		MethodContentPackage methodContentPackage = null;
		String name;
		
		for (int i = 0; i < allPluginElements.getLength(); i++) 
		{
			String id = ((Element)allPluginElements.item(i)).getAttribute("id");
			name = ((Element)allPluginElements.item(i)).getAttribute("name");
			
			methodContentPackage = (MethodContentPackage) factory.createElementById("");
			methodContentPackage.setId(id);
			methodContentPackage.setName(name);
					
			result.add(methodContentPackage);
			
		}
		
		return result;
	}
	
	private List<ProcessPackage> getProcessPackagesElements(Element notationElement)
	{
		List<ProcessPackage> result = new ArrayList<ProcessPackage>();
		NodeList allPluginElements = notationElement.getElementsByTagName("process-package");
		ProcessPackage processPackage = null;
		String name;
		
		for (int i = 0; i < allPluginElements.getLength(); i++) 
		{
			String id = ((Element)allPluginElements.item(i)).getAttribute("id");
			name = ((Element)allPluginElements.item(i)).getAttribute("name");
			
			processPackage = (ProcessPackage) factory.createElementById("");
			processPackage.setId(id);
			processPackage.setName(name);
					
			//Read method content packages
			Element processElement = (Element)allPluginElements.item(i);
			NodeList lst = processElement.getElementsByTagName("ownedMembers");
			Node node = lst.item(0);
					
			if(node != null)
			{
				//System.out.println(String.format("notationElement %d %s %s", i, methodPluginElement.getNodeName(), node));
				List<ProcessPackageableElement> listContentPackages = getProcessElements((Element)node);
				processPackage.setOwnedMembers(listContentPackages);
			}
					
			result.add(processPackage);
			
		}
		
		return result;
	}
	
	private List<ProcessPackageableElement> getProcessElements(Element notationElement)
	{
		List<ProcessPackageableElement> result = new ArrayList<ProcessPackageableElement>();
		NodeList allPluginElements = notationElement.getElementsByTagName("process-package");
		ProcessPackage processPackage = null;
		String name;
		
		for (int i = 0; i < allPluginElements.getLength(); i++) 
		{
			String id = ((Element)allPluginElements.item(i)).getAttribute("id");
			name = ((Element)allPluginElements.item(i)).getAttribute("name");
			
			processPackage = (ProcessPackage) factory.createElementById("");
			processPackage.setId(id);
			processPackage.setName(name);
			
			result.add(processPackage);
			
		}
		
		return result;
	}
	
	public SPERootElement getMethodLibrary(IFile file)
	{
		Document doc = getDocument(file);
		SPERootElement rootElement = null;
		
		if(doc != null)
		{
			Element root = doc.getDocumentElement();
			if( !("spe-root".equals(root.getNodeName())) )
				return null;
			
			String name = root.getAttribute("name");
			String id = root.getAttribute("id");
			
			rootElement = getSPERootElement(root);
			rootElement.setId(id);
			rootElement.setName(name);
		}
		
		return rootElement;
	}
	
	private SPERootElement getSPERootElement(Element root)
	{
		NodeList children = root.getChildNodes();
		String elementName;
		int i = 0;
		SPERootElement re = new SPERootElement();
		
		for(i=0; i<children.getLength(); i++)
		{
			Node node = children.item(i);
			if(node.getNodeType() == Node.ELEMENT_NODE)
			{
				Element elem = (Element)node;
				elementName = elem.getNodeName();
				
				String id = elem.getAttribute("id");
				String name = elem.getAttribute("name");
				
				if(elementName.equals("diagram"))
				{
					SPEMDiagram diagram = (SPEMDiagram) factory.createElementById("spe.model.diagram.SPEMDiagram");
					diagram.setId(id);
					re.addChildElement(diagram);
				}
				else if(elementName.equals("method-library"))
				{
					MethodLibrary ml = (MethodLibrary) factory.createElementById("spe.model.spem.method.plugin.MethodLibrary");
					ml.setId(id);
					re.addChildElement(ml);
					if(name != null)
						ml.setName(name);
				}
				else if(elementName.equals("method-plugin"))
				{
							
				}
				else if(elementName.equals("method-configuration"))
				{
					
				}
				else if(elementName.equals("activity"))
				{
				
				}
				else if(elementName.equals("work-definition"))
				{
				
				}
				else if(elementName.equals("planning-data"))
				{
					
				}
			}
		}
		
		return re;
	}
	
	public boolean isSynchronized(Node node, SPEMElement element)
	{
		return false;
	}
	
	private String rootMode2XmlString()
	{
		StringWriter writer = new StringWriter();
		try {
			Document document = documentBuilderFactory.newDocumentBuilder().newDocument();
			Element root = document.createElement("method-library");
			//addAttribute(root, "id", rootSPEElement.getId());
			//addAttribute(root, "name", rootSPEElement.getName());
			
			document.appendChild(root);
			
			write(rootSPEElement, document, root);
			
			DOMSource domSource = new DOMSource(root);
			Transformer transformer = transformerFactory.newTransformer();
			transformer.setOutputProperty(OutputKeys.INDENT, "yes");
			StreamResult streamResult = new StreamResult(writer);
			transformer.transform(domSource, streamResult);
			//transformer.transform(domSource, new StreamResult(System.out));
			return writer.toString();
		} catch (Exception e) {
			Logger.logError("Problem while saving to disk", e);
			return null;
		}
	}
	
	private String getXmlElementName(SPEMElement element)
	{
		IConfigurationElement config = xmlRegistry.getConfigurationElementBySemanticElementId(element.getElementId());
		return config.getAttribute("xmlElement");
	}
	
	private void write(SPERootElement rootElement, Document document, Element parent)
	{
		int i;
		SPEMElement[] elements = rootElement.getElements();
		String elementName, key;
		Element domElement;
		for(i=0; i<elements.length; i++)
		{
			elementName = getXmlElementName(elements[i]);
			domElement = document.createElement(elementName);
			
			Map<String, Object> attrs = elements[i].getAttributes();
			Iterator<String> iter = attrs.keySet().iterator();
			while(iter.hasNext())
			{
				key = iter.next();
				domElement.setAttribute(key, attrs.get(key).toString());
			}
			
			insertChild(document, parent, domElement, elements[i]);	
		}
	} 
	
	private void insertChild(Document document, Element parent, Element domElement, SPEMElement spemElement)
	{
		boolean existed = false;
		String elementName;
		if( PackageElement.class.isInstance( spemElement ))
		{
			PackageElement pe = (PackageElement)spemElement;
			SPEMElement[] children = pe.getChildElements().toArray(new SPEMElement[pe.getChildElements().size()]);
			for(int i=0;i<children.length;i++)
			{
				elementName = getXmlElementName(children[i]);
				existed = isElementExistedInRoot( elementName, children[i].getId(), parent);
				
				if(!existed)
				{
					
				}
			}
		}
		parent.appendChild(domElement);
	}

	private boolean isElementExistedInRoot(String elementName, String id, Element parent)
	{
		NodeList lst = parent.getChildNodes();
		for(int i=0; i<lst.getLength(); i++)
			if(lst.item(i).getNodeType() != Node.TEXT_NODE)
			{
				Element e = (Element)lst.item(i);
				if( e.getNodeName().equals(elementName) 
						&& e.getAttribute("id").equals(id)) 
					return true;
			}
		
		return false;
	}
	
	private void write(MethodConfiguration cfg, Document document, Element parent) 
	{
		Element member = document.createElement("method-configuration");
		addAttribute(member, "id", cfg.getId());
		addAttribute(member, "name", cfg.getName());
		parent.appendChild(member);
	}
	
	private void write(MethodPlugin cfg, Document document, Element parent) 
	{
		Element methodPluginElement = document.createElement("method-plugin");
		addAttribute(methodPluginElement, "id", cfg.getId());
		addAttribute(methodPluginElement, "name", cfg.getName());
		
		//write ownedMethodContentPackages
		Element member;
		Iterator<MethodContentPackage> iter = cfg.getOwnedMethodContentPackages().iterator();
		while(iter.hasNext())
		{
			MethodContentPackage mcp = iter.next();
			member = document.createElement("method-content-package");
			addAttribute(member, "id", mcp.getId());
			addAttribute(member, "name", mcp.getName());
			methodPluginElement.appendChild(member);
		}
		
		//write processPackage
		Iterator<ProcessPackage> iterProcess = cfg.getOwnedProcessPackages().iterator();
		while(iterProcess.hasNext())
		{
			ProcessPackage pp = iterProcess.next();
			member = document.createElement("process-package");
			addAttribute(member, "id", pp.getId());
			addAttribute(member, "name", pp.getName());
			methodPluginElement.appendChild(member);
		}
		
		parent.appendChild(methodPluginElement);
	}

	private void addAttribute(Element e, String attributeName, String value) 
	{
		if (value != null) {
			Attr attr = e.getOwnerDocument().createAttribute(attributeName);
			attr.setNodeValue(value);
			e.setAttributeNode(attr);
		}
	}

	public boolean saveToInput(IEditorInput diagramInput, SPEMDiagram diagram, IEditorInput srcLibInput, SPERootElement rootElement) 
	{
		boolean result = true;
		rootSPEElement = rootElement;
		try {
			//Save source lib
			IFile file = ((IFileEditorInput)srcLibInput).getFile();
			Element rootDomElement = documentBuilderFactory.newDocumentBuilder().parse(file.getContents()).getDocumentElement();
			if (!isSynchronized(rootDomElement, rootSPEElement)) 
			{
				if(file.exists())
				{
					//file.setContents(new ByteArrayInputStream(toNotationInfoXml(rootElement).getBytes()), true, true, null);
					return true;
				}
				
				//file.create(new ByteArrayInputStream(toNotationInfoXml(mainElement).getBytes()), true, null);
				return true;
			}
			result = false;
		} catch (Exception e) 
		{
			e.printStackTrace();
			Logger.logError("Problem while saving the input.", e);
		}
		return result;
	}
	
	public boolean saveToInput(IEditorInput input, Document document)
	{
		try {
			IFile file = ((IFileEditorInput)input).getFile();
			StringWriter writer = new StringWriter();
			Element root = document.getDocumentElement();
			
			
			DOMSource domSource = new DOMSource(root);
			Transformer transformer = transformerFactory.newTransformer();
			//transformer.setOutputProperty(OutputKeys.INDENT, "yes");
			StreamResult streamResult = new StreamResult(writer);
			transformer.transform(domSource, streamResult);
			//transformer.transform(domSource, new StreamResult(System.out));
			
			if(file.exists())
			{
				file.setContents(new ByteArrayInputStream(writer.toString().getBytes()), true, true, null);
				return true;
			}
			
			file.create(new ByteArrayInputStream(writer.toString().getBytes()), true, null);
			return true;
			
		} catch (Exception e) {
			Logger.logError("Problem while saving to disk", e);
			return false;
		}
	}
}
